﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.IO;
using System.Linq;
using System.Threading;
using FileMonitor.Configuration.Prototype.BizProcess;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
using FileUtility.Log;
using FileMonitor.Service.TimerTrriger;
using Manager = FileMonitor.Service.ProcessMonitor.Manager;
using FileMonitor.Configuration.Prototype.Provider;
using FileMonitor.Configuration.Prototype;


namespace FileMonitor.Service.Provider.FileMonitor
{
   

    public enum ProcessState
    {
        Starting,
        Waitting,
        Running,
        Pause,
        Error
    }

    public abstract class FileMonitorBase :ProviderBase, TimerTrriger.ITriggerProcess, IDisposable
    {
        Queue<string> queueFiles ;//= new Queue<string>();
        EventWaitHandle _wh = new AutoResetEvent(false);
        Thread _worker;
        private readonly object _locker = new object();
        public Microsoft.Practices.EnterpriseLibrary.Data.Sql.SqlDatabase exchangeDB;
      
        protected  FileProcessConfiguration baseConfiguration;
       // protected SplitFlag splitFlag = SplitFlag.CHAR;
        protected Dictionary<int, BizProcessBase> _dicBizProcesses;
        private Dictionary<Guid, int> failureTasksCount;
        Utility.FileUtil util;
        //private NameValueCollection AllConfig;
         
        private int moving_Mutex = 0;
        private object objMutex = new object();
        private ProcessState processState;
      
        #region ProviderBase Members
        public override void Initialize(string name, NameValueCollection config)
        {
            base.Initialize(name, config);

            util = new Utility.FileUtil();

            if (config["MonitorPath"] == null)
            {
                throw new ArgumentException("Must_Set_MonitorPath");
            }
            
            exchangeDB = new SqlDatabase(System.Configuration.ConfigurationManager.ConnectionStrings["DefaultExchangeDB"].ConnectionString);

            baseConfiguration = new  FileProcessConfiguration();
            baseConfiguration.Initialize(config);

            this._worker = new Thread(Work);
            this.queueFiles = new Queue<string>();
            this.failureTasksCount = new Dictionary<Guid, int>();
        }
        #endregion

        public string MonitorPath
        {
            get 
            {
                return this.baseConfiguration.MonitorPath;
            }
        }

        public ProcessState ProcessState
        {
            get
            {
                return this.processState;
            }
        }

        public void Start()
        {
            this._worker.Start();
            this._dicBizProcesses.ToList().ForEach(bizP => {
                bizP.Value.StartWorkThread();
            });
            TimerTrriger.Trriger.RegisterTriggerProcess(this);
            processState = ProcessState.Running;
        }

        public void Stop()
        {
            StopWorkThread();
            this._dicBizProcesses.ToList().ForEach(bizP =>
            {
                bizP.Value.StopWorkThread();
            });
            TimerTrriger.Trriger.UnRegisterTriggerProcess(this);
            processState = ProcessState.Pause;
        }

        //Can be Produced
        public void Enqueue(string fileFullPath)
        {
            lock (_locker)
            {
                queueFiles.Enqueue(fileFullPath);
            }
            _wh.Set();
        }

        private void StopWorkThread()
        {
            this.Enqueue(string.Empty);

        }

        private void Work()
        {
            while (true)
            {
                string fileFullPath = string.Empty;
                lock (_locker)
                {
                    if (this.queueFiles.Count > 0)
                    {
                        fileFullPath = this.queueFiles.Dequeue();
                        if (string.IsNullOrEmpty(fileFullPath))
                        {
                            return;
                        }
                    }
                }
                if (!string.IsNullOrEmpty(fileFullPath))
                {
                    FileSystemEventArgs args = util.InitializeFileEventArgs(fileFullPath, WatcherChangeTypes.Created);
                    Process(args);
                }
                else
                    _wh.WaitOne();
            }
        }

        #region Abstract Memebers
        public abstract void ProcessCreated(FileSystemEventArgs e);
        public abstract void ProcessChanged(FileSystemEventArgs e);
        public abstract void ProcessDeleted(FileSystemEventArgs e);
        #endregion

        #region ITriggerProcess Members
        public void TrrigerProcess()
        {
            MoveFile(this);
        }
        #endregion

        #region File Process Logic

        private void Process(FileSystemEventArgs e)
        {
            int waitTime = 1;
            Int32.TryParse(this.baseConfiguration.WaitTime, out waitTime);
            if (waitTime == 0)
            {
                waitTime = 1;
            }
            System.Threading.Thread.Sleep(waitTime * 1000);
            try
            {

                if (System.IO.File.Exists(e.FullPath))
                {
                    this.ProcessCreated(e);
                    string backupPath = this.baseConfiguration.BackUpPath + "\\" + e.Name;
                    if (System.IO.File.Exists(backupPath))
                    {
                        System.IO.File.Delete(backupPath);
                    }

                    if (System.IO.File.Exists(e.FullPath))
                    {
                        System.IO.File.Move(e.FullPath, backupPath);
                    }
                    Manager.ProcessStateMonitorManager.Current.CompleteFile(e.FullPath, backupPath, this);
                }
            }
            catch (Exception ex)
            {
                string errorPath = e.FullPath + ".Error";
                if (System.IO.File.Exists(errorPath))
                {
                    System.IO.File.Delete(errorPath);
                }
                if (System.IO.File.Exists(e.FullPath))
                {
                    System.IO.File.Move(e.FullPath, e.FullPath + ".Error");
                }
                Manager.ProcessStateMonitorManager.Current.LogErrorFile(e.FullPath, errorPath, this, ex.Message);
                Log.getInstance().WriteToLog(System.DateTime.Now.ToString() + "-------" + e.Name + "---------" + ex.Message, baseConfiguration.LogPath);
            }
        }

        private void MoveFile(object obj)
        {
            FileMonitorBase baseObj = obj as FileMonitorBase;
            try
            {
                if (Interlocked.Exchange(ref baseObj.moving_Mutex, 1) == 0)
                {
                    string path = baseObj.baseConfiguration.MonitorPath;
                    string[] files = System.IO.Directory.GetFiles(path);
                    if (files.Length > 0)
                    {
                        foreach (string fileName in files)
                        {
                            List<string> targetFiles = baseObj.util.MovePeocessFileReturnTarget(fileName, path, baseObj.baseConfiguration);
                            targetFiles.ForEach(f =>
                            {
                                Manager.ProcessStateMonitorManager.Current.AddNewFile(f, this);
                                this.Enqueue(f);
                            });
                        }
                    }
                    Interlocked.Exchange(ref baseObj.moving_Mutex, 0);
                }
            }
            catch (Exception ex)
            {
                Log.getInstance().WriteToLog(ex.Message, baseConfiguration.LogPath);
            }
            finally
            {
                Interlocked.Exchange(ref baseObj.moving_Mutex, 0);
                
            }
        }

        #endregion

        #region Biz Process Logic
        public void CopyBizProcesses(Dictionary<int, BizProcessBase> sourceProcess)
        {
            if (this._dicBizProcesses == null)
            {
                this._dicBizProcesses = new Dictionary<int, BizProcessBase>();
            }
            this._dicBizProcesses.Clear();
            foreach (int key in sourceProcess.Keys)
            {
                this._dicBizProcesses.Add(key, sourceProcess[key].Copy());
                this._dicBizProcesses[key].Excuted += new BizProcessBase.ExcutedHander(FileMonitorBase_Excuted);
            }
            foreach (int key in this._dicBizProcesses.Keys) 
            {
                if (this._dicBizProcesses.ContainsKey(key + 1))
                {
                    this._dicBizProcesses[key].NextBizProcess = this._dicBizProcesses[key + 1];
                }
            }
        }

        void FileMonitorBase_Excuted(object sender, ExcutedEventArgs args)
        {
            if (args.Result == ExcutedResult.Successful)
            {
                UpdateBizSucessful(new Guid(args.Task["BatchId"]), args.NextStatus);
            }
            //When Biz runned failed,it will repeat 3 times.
            else if (args.Result == ExcutedResult.Failure)
            {
                Guid batchid = new Guid(args.Task["BatchId"]);
                if (this.failureTasksCount.Keys.Contains(batchid))
                {
                    int countTimes = this.failureTasksCount[batchid];
                    if (countTimes < 3)
                    {
                        NotifyRunBizProcess(args.Task);
                        this.failureTasksCount[batchid] = this.failureTasksCount[batchid] + 1;
                    }
                    else
                    {
                        UpdateBizFailure(batchid, -2);
                        this.failureTasksCount.Remove(batchid);
                    }
                }
                else
                {
                    NotifyRunBizProcess(args.Task);
                    this.failureTasksCount.Add(batchid, 1);
                }
            }
        }

        private void UpdateBizSucessful(Guid batchid, int status)
        {
            FileUtility.DBUtility.UpdateStatus(this.exchangeDB, batchid, status);
        }
        private void UpdateBizFailure(Guid batchid, int status)
        {
            FileUtility.DBUtility.UpdateStatus(this.exchangeDB, batchid, status);
        }

        protected void NotifyRunBizProcess(NameValueCollection task)
        {
            if (task["BatchId"] == null)
            {
                throw new ArgumentException("Can't be null", "BatchId");
            }
            if (this._dicBizProcesses.Count > 0)
            {
                this._dicBizProcesses.First().Value.Enqueue(task);
            }
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            

        }
        #endregion

    }
}
